// Copyright (C) 2005 Gameloft. All rights reserved.
// GAMELOFT PROPRIETARY/CONFIDENTIAL.
//
// Author: Qiu Wei Min

#include "ResFile.h"
#include "Stream.h"
#include "Memory.h"
#include "Debug.h"

CResFile _resPack[20];
#define _USE_LZO
//#define _USE_LZMO

#ifdef _USE_LZO
int decompress  ( const unsigned char  *in , unsigned int  in_len,
				  unsigned char  *out, unsigned int *out_len);
#elif defined _USE_LZMO
#include "LzmaDecode.h"
#endif

CResFile::CResFile() : m_recordNumber(0), m_records(NULL), m_file(NULL)
{
}

CResFile::~CResFile()
{
	Close();
}
enum PackedFlag
{
	RECORD_NUM,
	RECORD
};
void CResFile::Initialize(const char * packedFile)
{
	Read(packedFile, this);
}

void CResFile::LoadFile(void* object, CFileStream *pFS)
{
	CResFile * pThis = (CResFile*)object;
	pThis->m_file = GL_NEW CMemoryStream();
	pThis->m_file->AttachCopy(*pFS);
#ifdef _USE_LZMO
	if(pThis->m_file->ReadIntLE() != 0x4b504c47)
	{
		ASSERT(false);
		return;
	}
	pThis->m_recordNumber = pThis->m_file->ReadIntLE();
#else
	pThis->m_recordNumber = pThis->m_file->ReadIntLE();
#endif
	
	ASSERT( pThis->m_file->getLength() - pThis->m_file->getPosition() >= (pThis->m_recordNumber + 1) * (int)sizeof(Record));

	pThis->m_records = GL_NEW CResFile::Record[pThis->m_recordNumber + 1];

	int i;

	for(i = 0; i < pThis->m_recordNumber + 1; i++)
	{
		pThis->m_records[i].m_hash = pThis->m_file->ReadIntLE();
		pThis->m_records[i].m_offset = pThis->m_file->ReadIntLE();
	}
}

void CResFile::Close()
{
	SAFE_DELETE_ARRAY(m_records);
	if(m_file != null)
	{
		SAFE_DELETE(m_file->mpData);
	}
	SAFE_DELETE(m_file);	
	m_recordNumber = 0;
}

void* CResFile::FindRecord(const char *pName)
{
	if(m_file==NULL || m_recordNumber <= 0)
		return NULL;
	unsigned long h = ComputeHash(pName);
	int i, u, l;
	l = 0;
	u = m_recordNumber - 1;
	i = m_recordNumber / 2;
	if( m_records == NULL)
		return NULL;
	/*for(int i=0; i<= m_recordNumber; ++i )
	{
		if(m_records[i].m_hash == h)
			return m_records+i;
	}
	return NULL;
	*/
	while(m_records[i].m_hash != h)
	{
		if(u <= l)
		{
			//record not found
			return NULL;
		}
		if(m_records[i].m_hash > h)
			u = i - 1;
		else
			l = i + 1;
		i = (u + l) / 2;
	}
	return m_records + i;
	
}

unsigned long CResFile::ComputeHash(const char *s)
{
	unsigned long h=0;
	const char *p;
	for(p = s; *p ; p ++)
		h = ( h << 5 ) - h + (*p == '/' ? '\\' : *p);
	return h;
}

unsigned int CResFile::GetRecordPackedSize(void* hRecord)
{
	Record *p = (Record*)hRecord;

	ASSERT(p >= m_records && p < m_records + m_recordNumber);
	return p[1].m_offset - p[0].m_offset;
}

// Size before compression
unsigned int CResFile::GetRecordSize(void *hRecord)
{
	Record *p = (Record*)hRecord;
	m_file->SetPosition(p->m_offset);
	return m_file->ReadUIntLE();
}

unsigned int CResFile::ReadRecord(void *hRecord, void *pBuf, unsigned int len)
{
#if defined(_USE_LZO)||defined(_USE_LZMO)
	unsigned int originalSize;
	unsigned int packedSize = GetRecordPackedSize(hRecord);
	m_file->SetPosition(((Record*)hRecord)->m_offset);
	unsigned int outSizeProcessed;
	packedSize -= 4;
	originalSize = m_file->ReadUIntLE();
	if(originalSize > len)
	{
		ASSERT(false);
		return 0;
	}
#else
	unsigned int originalSize;
	unsigned int outSizeProcessed;

	unsigned int packedSize = GetRecordPackedSize(hRecord);
	m_file->SetPosition(((Record*)hRecord)->m_offset);
	outSizeProcessed = m_file->Read(pBuf, packedSize);
#endif

#ifdef _USE_LZO	
	//char *tempBuffer = GL_NEW char[packedSize];
	//m_file->Read(tempBuffer, packedSize);
	if(decompress((const unsigned char*)(m_file->getData())+m_file->getPosition(),packedSize,(unsigned char*)pBuf,&outSizeProcessed))
	//if(decompress((const unsigned char*)tempBuffer,packedSize,(unsigned char*)pBuf,&outSizeProcessed))
	{
		ASSERT(false);
	}
	//delete []tempBuffer;
#elif defined _USE_LZMO
	unsigned char *inBuf = m_file.getData();//NEW unsigned char[packedSize];
	//unsigned char *inBuf = GL_NEW unsigned char[packedSize];
	//m_file->Read(inBuf, packedSize);
	//m_file->Read(inBuf, packedSize);

	int lc, lp, pb;
	unsigned int lzmaInternalSize;
	unsigned char *lzmaInternalData;
    lc = inBuf[0];
    for (pb = 0; lc >= (9 * 5); pb++, lc -= (9 * 5));
    for (lp = 0; lc >= 9; lp++, lc -= 9);
	lzmaInternalSize = (LZMA_BASE_SIZE + (LZMA_LIT_SIZE << (lc + lp)))* sizeof(CProb);

	lzmaInternalData = GL_NEW unsigned char[lzmaInternalSize];
	
	
    if(LzmaDecode((unsigned char *)lzmaInternalData, lzmaInternalSize,
		lc, lp, pb, inBuf + 1, packedSize,
		(unsigned char *)pBuf, originalSize, &outSizeProcessed))
	{
		ASSERT(false);
	}

	ASSERT(outSizeProcessed == originalSize);
	
	SAFE_DELETE_ARRAY(lzmaInternalData);
	SAFE_DELETE_ARRAY(inBuf);
#endif
	return outSizeProcessed;
}

#ifdef _USE_LZO
int decompress  ( const unsigned char  *in , unsigned int  in_len,
				 unsigned char  *out, unsigned int *out_len)
{
	register unsigned char  *op;
	register const unsigned char  *ip;
	register unsigned int t;
	register const unsigned char  *m_pos;
	const unsigned char  * const ip_end = in + in_len;

	*out_len = 0;
	op = out;
	ip = in;
	if (*ip > 17)
	{
		t = *ip++ - 17;
		if (t < 4)
			goto match_next;
		//		ASSERT(t > 0);
		do *op++ = *ip++; while (--t > 0);
		goto first_literal_run;
	}
	while (1)
	{
		t = *ip++;
		if (t >= 16)
			goto match;
		if (t == 0)
		{			
			while (*ip == 0)
			{
				t += 255;
				ip++;				
			}
			t += 15 + *ip++;
		}
		//		ASSERT(t > 0); 
		{
			*op++ = *ip++; *op++ = *ip++; *op++ = *ip++;
			do *op++ = *ip++; while (--t > 0);
		}
first_literal_run:
		t = *ip++;
		if (t >= 16)
			goto match;
		m_pos = op - (1 + 0x0800);
		m_pos -= t >> 2;
		m_pos -= *ip++ << 2;

		*op++ = *m_pos++; *op++ = *m_pos++; *op++ = *m_pos;
		goto match_done;
		while (1)
		{
match:
			if (t >= 64)
			{
				m_pos = op - 1;
				m_pos -= (t >> 2) & 7;
				m_pos -= *ip++ << 3;
				t = (t >> 5) - 1;
				//				ASSERT(t > 0);
				goto copy_match;
			}
			else if (t >= 32)
			{
				t &= 31;
				if (t == 0)
				{
					while (*ip == 0)
					{
						t += 255;
						ip++;
					}
					t += 31 + *ip++;
				}
				m_pos = op - 1;
				m_pos -= (ip[0] >> 2) + (ip[1] << 6);
				ip += 2;
			}
			else if (t >= 16)
			{
				m_pos = op;
				m_pos -= (t & 8) << 11;
				t &= 7;
				if (t == 0)
				{
					while (*ip == 0)
					{
						t += 255;
						ip++;
					}
					t += 7 + *ip++;
				}
				m_pos -= (ip[0] >> 2) + (ip[1] << 6);
				ip += 2;
				if (m_pos == op)
					goto eof_found;
				m_pos -= 0x4000;
			}
			else
			{
				m_pos = op - 1;
				m_pos -= t >> 2;
				m_pos -= *ip++ << 2;
				*op++ = *m_pos++; *op++ = *m_pos;
				goto match_done;
			}
			//			ASSERT(t > 0); 
			{
copy_match:
				*op++ = *m_pos++; *op++ = *m_pos++;
				do *op++ = *m_pos++; while (--t > 0);
			}
match_done:
			t = ip[-2] & 3;
			if (t == 0)
				break;
match_next:
			//			ASSERT(t > 0);
			do *op++ = *ip++; while (--t > 0);
			t = *ip++;
		}
	}
eof_found:
	//	ASSERT(t == 1);
	*out_len = op - out;
	return (ip == ip_end ? 0 :
		(ip < ip_end  ? (-8) : (-4)));
}
#endif
